/*
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License
* Version 1.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://www.sun.com/
*
* The Original Code is Forte for Java, Community Edition. The Initial
* Developer of the Original Code is Sun Microsystems, Inc. Portions
* Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved.
*/
package org.netbeans.editor.ext;
import java.util.ArrayList;
import java.util.HashMap;
import javax.swing.text.BadLocationException;
import org.netbeans.editor.BaseDocument;
import org.netbeans.editor.TokenProcessor;
/**
* Mapping of colorings to particular token types
*
* @author Miloslav Metelka
* @version 1.00
*/
public class JavaImport implements TokenProcessor {
/** Initial length of the document to be scanned. It should be big enough
* so that only one pass is necessary. If the initial section is too
* long, then this value is doubled and the whole parsing restarted.
*/
private static final int INIT_SCAN_LEN = 4096;
private static final int INIT = 0; // at the line begining before import kwd
private static final int AFTER_IMPORT = 1; // right after the import kwd
private static final int INSIDE_EXP = 2; // inside import expression
// inside import expression mixed from several different tokens
// exp string buffer is used in this case
private static final int INSIDE_MIXED_EXP = 3;
/** Short names to classes map
* @associates JCClass*/
private HashMap name2Class = new HashMap(501);
private char[] buffer;
/**
* @associates Info
*/
private ArrayList infoList = new ArrayList();
/** Current state of the imports parsing */
private int state;
/** Whether parsing package statement instead of import statment.
* They have similair syntax so only this flag distinguishes them.
*/
private boolean parsingPackage;
/** Start of the whole import statement */
private int startPos;
/** Start position of the particular import expression */
private int expPos;
private boolean eotReached;
private StringBuffer exp = new StringBuffer();
/** Whether the star was found at the end of package expression */
private boolean star;
JavaSyntax debugSyntax = new JavaSyntax(); // !!! debugging syntax
public JavaImport() {
}
public synchronized void update(BaseDocument doc) {
doc.readLock();
try {
int scanLen = INIT_SCAN_LEN;
int docLen = doc.getLength();
boolean wholeDoc = false;
do {
if (scanLen >= docLen) {
scanLen = docLen;
wholeDoc = true;
}
eotReached = false;
init();
try {
doc.getSyntaxSupport().tokenizeText(this, 0, scanLen, false);
} catch (BadLocationException e) {
if (Boolean.getBoolean("netbeans.debug.exceptions")) { // NOI18N
e.printStackTrace();
}
}
scanLen *= 4; // increase the scanning size
} while (!wholeDoc && eotReached);
} finally {
doc.readUnlock();
}
buffer = null;
}
protected void init() {
exp.setLength(0);
star = false;
parsingPackage = false;
infoList.clear();
name2Class.clear(); // clear current mappings
// add java.lang package by default
JCPackage pkg = JCompletion.getFinder().getExactPackage("java.lang"); // NOI18N
if (pkg != null) {
JCClass[] classes = pkg.getClasses();
for (int i = 0; i < classes.length; i++) {
name2Class.put(classes[i].getName(), classes[i]);
}
}
}
public JCClass getClazz(String className) {
JCFinder finder = JCompletion.getFinder();
JCClass ret = finder.getExactClass(className); // first try exact match
if (ret == null) {
ret = (JCClass)name2Class.get(className);
}
return ret;
}
protected void packageStatementFound(int packageStartPos, int packageEndPos, String packageExp) {
JCPackage pkg = JCompletion.getFinder().getExactPackage(packageExp);
if (pkg != null) {
JCClass[] classes = pkg.getClasses();
for (int i = 0; i < classes.length; i++) {
name2Class.put(classes[i].getName(), classes[i]);
}
}
}
protected void importStatementFound(int importStartPos, int importEndPos, String importExp, boolean starAtEnd) {
JCFinder finder = JCompletion.getFinder();
Info info = new Info(importStartPos, importEndPos, starAtEnd);
JCClass cls = finder.getExactClass(importExp);
if (cls != null) {
info.cls = cls;
if (star) { // !!! dodelat
} else { // only this single class
name2Class.put(cls.getName(), cls);
}
} else { // not a direct class, try package
JCPackage pkg = finder.getExactPackage(importExp);
if (pkg != null) {
info.pkg = pkg;
if (starAtEnd) { // only useful with star
JCClass[] classes = pkg.getClasses();
for (int i = 0; i < classes.length; i++) {
name2Class.put(classes[i].getName(), classes[i]);
}
}
} else { // not package, will be class
String pkgName = importExp;
String simplePkgName = null;
int ind;
while((ind = pkgName.lastIndexOf('.')) >= 0) {
pkgName = pkgName.substring(0, ind);
if (simplePkgName == null) {
simplePkgName = pkgName;
}
pkg = finder.getExactPackage(pkgName);
if (pkg != null) { // found valid package, but unknown class
cls = JCompletion.getSimpleClass(importExp, pkgName.length());
info.cls = cls;
if (star) {
// don't add in this case, can change in the future
} else {
name2Class.put(cls.getName(), cls);
}
break;
}
}
if (cls == null) {
// didn't found a direct package, assume last is class name
if (simplePkgName != null) { // at least one dot in importExp
cls = JCompletion.getSimpleClass(importExp, simplePkgName.length());
if (star) {
// don't add in this case, can change in the future
} else {
name2Class.put(cls.getName(), cls);
}
}
}
}
}
infoList.add(info);
}
public boolean token(int tokenID, int helperID, int offset, int tokenLen) {
boolean cont = true;
switch (tokenID) {
case JavaSyntax.IDENTIFIER:
switch (state) {
case AFTER_IMPORT:
expPos = offset;
state = INSIDE_EXP;
break;
case INSIDE_MIXED_EXP:
exp.append(buffer, offset, tokenLen);
// let it flow to INSIDE_EXP
case INSIDE_EXP:
if (star) { // not allowed after star was found
cont = false;
}
break;
}
break;
case JavaSyntax.OPERATOR:
switch (helperID) {
case JavaSyntax.DOT:
switch (state) {
case INIT: // ignore standalone dot
break;
case AFTER_IMPORT:
cont = false; // dot after import keyword
break;
case INSIDE_MIXED_EXP:
exp.append('.');
// let it flow to INSIDE_EXP
case INSIDE_EXP:
if (star) { // not allowed after star was found
cont = false;
}
break;
}
break;
case JavaSyntax.SEMICOLON:
String impExp = null;
switch (state) {
case INIT: // ignore semicolon
break;
case AFTER_IMPORT: // semicolon after import kwd
cont = false;
break;
case INSIDE_EXP:
impExp = new String(buffer, expPos,
(star ? (offset - 2) : offset) - expPos);
break;
case INSIDE_MIXED_EXP:
impExp = exp.toString();
exp.setLength(0);
break;
}
if (impExp != null) {
if (parsingPackage) {
packageStatementFound(startPos, offset + 1, impExp);
} else { // parsing import statement
importStatementFound(startPos, offset + 1, impExp, star);
}
star = false;
parsingPackage = false;
state = INIT;
}
break;
case JavaSyntax.MUL:
if (star || parsingPackage) {
cont = false;
} else {
switch (state) {
case INIT: // ignore star at the begining
break;
case AFTER_IMPORT:
cont = false; // star after import kwd
break;
case INSIDE_EXP:
star = true;
if (offset == 0 || buffer[offset - 1] != '.') {
cont = false;
}
break;
case INSIDE_MIXED_EXP:
int len = exp.length();
if (len > 0 && exp.charAt(len - 1) == '.') {
exp.setLength(len - 1); // remove ending dot
star = true;
} else { // error
cont = false;
}
break;
}
}
break;
default:
cont = false;
break;
}
break;
case JavaSyntax.KEYWORD: // KEYWORD found
switch (helperID) {
case JavaKeywords.PACKAGE:
switch (state) {
case INIT:
parsingPackage = true;
state = AFTER_IMPORT; // the same state is used
break;
default:
cont = false; // error in other states
break;
}
break;
case JavaKeywords.IMPORT: // IMPORT keyword
switch (state) {
case INIT:
parsingPackage = false;
state = AFTER_IMPORT;
startPos = offset;
break;
default:
cont = false; // error in other states
break;
}
break;
default:
cont = false;
}
break;
case JavaSyntax.TEXT:
case JavaSyntax.EOL:
case JavaSyntax.LINE_COMMENT:
case JavaSyntax.BLOCK_COMMENT:
switch (state) {
case INSIDE_EXP:
// Need to continue as string
exp.append(buffer, expPos, offset - expPos);
state = INSIDE_MIXED_EXP;
break;
}
break;
default:
cont = false;
}
return cont;
}
private String debugState(int state) {
switch (state) {
case INIT:
return "INIT"; // NOI18N
case AFTER_IMPORT:
return "AFTER_IMPORT"; // NOI18N
case INSIDE_EXP:
return "INSIDE_EXP"; // NOI18N
case INSIDE_MIXED_EXP:
return "INSIDE_MIXED_EXP"; // NOI18N
}
return "UNKNOWN STATE"; // NOI18N
}
public int eot(int offset) {
eotReached = true; // will be rescanned
return 0;
}
public void nextBuffer(char[] buffer, int offset, int len,
int startPos, int preScan, boolean lastBuffer) {
this.buffer = buffer;
}
class Info {
Info(int startPos, int endPos, boolean star) {
this.startPos = startPos;
this.endPos = endPos;
this.star = star;
}
int startPos;
int endPos;
boolean star;
JCPackage pkg;
JCClass cls;
}
}
/*
* Log
* 11 Gandalf-post-FCS1.9.1.0 3/8/00 Miloslav Metelka
* 10 Gandalf 1.9 1/13/00 Miloslav Metelka Localization
* 9 Gandalf 1.8 1/10/00 Miloslav Metelka
* 8 Gandalf 1.7 11/14/99 Miloslav Metelka
* 7 Gandalf 1.6 11/8/99 Miloslav Metelka
* 6 Gandalf 1.5 10/23/99 Ian Formanek NO SEMANTIC CHANGE - Sun
* Microsystems Copyright in File Comment
* 5 Gandalf 1.4 10/10/99 Miloslav Metelka
* 4 Gandalf 1.3 9/30/99 Miloslav Metelka
* 3 Gandalf 1.2 9/15/99 Miloslav Metelka
* 2 Gandalf 1.1 9/10/99 Miloslav Metelka
* 1 Gandalf 1.0 8/27/99 Miloslav Metelka
* $
*/